﻿using System;
using System.Threading;
using NUnit.Framework;
using OpenWaves.Caching;

namespace OpenWaves.UnitTests.Caching
{
    [TestFixture]
    public class AsyncConcurrentCacheTests : ConcurrentCacheTestsBase
    {
        [Test]
        public void GIVEN_value_is_in_cache_WHEN_value_is_stale_THEN_GetOrCreate_returns_stale_value()
        {
            var asyncConcurrentCache = new AsyncConcurrentCache<object>(
                new DictionaryCache<AsyncCacheEntry<object>>(),
                new MonitorBasedLockManager());

            var expectedValue = new object();

            using (var fakeTime = FakeTimeScope.Begin(new DateTime(2000, 1, 1)))
            {
                asyncConcurrentCache.InsertValue("key", expectedValue, TimeSpan.FromHours(1));

                fakeTime.Forward(TimeSpan.FromHours(2));    // Now it is stale

                var actualValue = asyncConcurrentCache.GetOrCreate("key", () => CacheEntry.FromValue(new object(), TimeSpan.FromHours(1)));

                Assert.That(actualValue, Is.SameAs(expectedValue));
            }
        }

        [Test]
        public void GIVEN_value_is_in_cache_WHEN_value_is_stale_THEN_GetOrCreate_refreshes_value_asynchronously()
        {
            var threadsCoordinator = new TestThreadsCoordinator();

            var asyncConcurrentCache = new AsyncConcurrentCache<string>(
                new DictionaryCache<AsyncCacheEntry<string>>(),
                new MonitorBasedLockManager(),
                threadsCoordinator);

            var expectedValue = "Value2";
            object actualValue = null;

            threadsCoordinator.RunThreads(() =>
                        {
                            using (var fakeTime = FakeTimeScope.Begin(new DateTime(2000, 1, 1)))
                            {
                                asyncConcurrentCache.InsertValue("key", "Value1", TimeSpan.FromHours(1));

                                fakeTime.Forward(TimeSpan.FromHours(2)); // Now it is stale

                                asyncConcurrentCache.GetOrCreate("key", () => CacheEntry.FromValue(expectedValue, TimeSpan.FromHours(1))); 
                                
                                threadsCoordinator.WaitForTick(1);  // Wait for refresh

                                actualValue = asyncConcurrentCache.GetOrCreate("key", () => null);
                            }
                        });

            Assert.That(actualValue, Is.SameAs(expectedValue));
        }

        protected override IConcurrentCache<object> CreateConcurrentCache(ILockManager lockManager)
        {
            return new AsyncConcurrentCache<object>(new DictionaryCache<AsyncCacheEntry<object>>(), lockManager);
        }
    }
}